import json
from datetime import date, datetime

from django.core.urlresolvers import reverse
from django.db.models import F, Sum
from django.db.models import Q
from django.http import HttpResponse

# Create your views here.
from django.http import HttpResponseRedirect
from django.http import JsonResponse
from django.shortcuts import redirect, render

# -------------------------------类视图-------------------------------
from django.template import loader
from django.utils.decorators import method_decorator
from django.views.generic import View  # 或者 from django.views.generic.base import View
from rest_framework.viewsets import ModelViewSet

from users_1.decorator import my_decorator

# 注: 类视图Mixin扩展类,一般在类名最后添加Mixin(如class BookMixin(object)),然后在定义类视图是继承这个扩展类
# name='dispatch'对类中dispatch方法装饰,而dispatch是根据请求方式来决定装饰的方法,因此是对全部请求方法添加装饰器
# 为特定请求方法添加装饰器, name="get"
# 如果为多个请求方式而不是全部来装饰,那就在方法上一个个添加@method_decorator(my_decorator)
from users_1.models import BookInfo, HeroInfo
from users_1.serializers import BookInfoSerializer

"""
利用HTTP请求方式可以分离网址中的资源名称的操作
    请求方式   处理功能             获取传递参数方式                返回值
    GET-------处理查询------------查询字符串?k1=v1&k2&v2...---->返回:状态码200,值:查询到的数据json
    POST------处理增加------------请求报文体json--------------->返回:状态码201,值:创建成功的数据
    PUT-------处理修改(全部更新)----请求报文体json--------------->返回:状态码201,值:修改成功的数据
    DELETE----处理删除------------无-------------------------->返回:状态码204,值:无
    PATCH-----处理修改(部分更新)----请求报文体json--------------->返回:状态码201,值:修改成功的数据

一个REST API即是一个视图函数
"""

# 利用序列化器将增删改查中的序列化/反序列化进行抽取,这是一个视图集合,ModelViewSet类继承了增/删/改/差类的一系列操作
class BookInfoViewSet(ModelViewSet):
    queryset = BookInfo.objects.all()  # queryset指明该视图集在查询数据时使用的查询集
    serializer_class = BookInfoSerializer  # 指明该视图在进行序列化/反序列化时使用的序列化器







class BooksAPIView(View):
    """
    查询所有图书/增加图书
    """
    def get(self, request):
        """
        查询所有图书
        路由: GET /users_1/books/
        :param request:
        :return:
        """
        # 获取所有图书对象,未传入参数
        queryset = BookInfo.objects.all()
        book_list = []
        # 将图书对象转为字典
        for book in queryset:
            book_list.append({
                "id": book.id,
                "btitle": book.btitle,
                "bpub_date": book.bpub_date,
                "bread": book.bread,
                "bcomment": book.bcomment,
                "bk_count": book.bk_count,
                "image": book.image.url if book.image else ""
            })
        # return JsonResponse(book_list, safe=False)  # 若要返回的是列表,那么需要设置safe=False,使得HTTP关闭检查是否为JSON数据(安全/解析输出)
        return JsonResponse({"books": book_list})

    def post(self, request):
        """
        新增图书
        路由: POST /users_1/books/
        :param request:
        :return:
        """
        # 获取请求体中参数
        book_dict = json.loads(request.body.decode())

        # 此处本应校验参数,此时略

        book = BookInfo.objects.create(
            btitle=book_dict.get("btitle"),
            bpub_date=datetime.strptime(book_dict.get("bpub_date"), "%Y-%m-%d").date()
        )

        return JsonResponse({
            "id": book.id,
            "btitle": book.btitle,
            "bpub_date": book.bpub_date,
            "bread": book.bread,
            "bcomment": book.bcomment,
            "bk_count": book.bk_count,
            "image": book.image.url if book.image else ""
        }, status=201)


class BookAPIView(View):
    def get(self, request, pk):
        """
        获取单个图书信息
        路由: GET /users_1/book/<pk>/
        :param request:
        :param pk: 路由上的参数
        :return:
        """
        try:
            book = BookInfo.objects.get(pk=pk)
        except BookInfo.DoesNotExist:
            return HttpResponse(status=404)
        return JsonResponse({
            'id': book.id,
            'btitle': book.btitle,
            'bpub_date': book.bpub_date,
            'bread': book.bread,
            'bcomment': book.bcomment,
            "bk_count": book.bk_count,
            'image': book.image.url if book.image else ''
        })

    def put(self,request, pk):
        """
        修改图书信息
        路由: PUT /users_1/book/<pk>/
        :param request:
        :param pk:
        :return:
        """
        try:
            book = BookInfo.objects.get(pk=pk)
        except BookInfo.DoesNotExist:
            return HttpResponse(status=404)
        book_dict = json.loads(request.body.decode())

        # 校验参数,略

        book.bittle = book_dict.get("btitle")
        book.bpub_date = datetime.strptime(book_dict.get('bpub_date'), '%Y-%m-%d').date()
        book.save()

        return JsonResponse({
            'id': book.id,
            'btitle': book.btitle,
            'bpub_date': book.bpub_date,
            'bread': book.bread,
            'bcomment': book.bcomment,
            "bk_count": book.bk_count,
            'image': book.image.url if book.image else ''
        })

    def delete(self, request, pk):
        """
        删除图书
        路由: DELETE /users_1/book/<pk>/
        :param request:
        :param pk:
        :return:
        """
        try:
            books = BookInfo.objects.filter(pk__gt=pk)
        except BookInfo.DoesNotExist:
            return HttpResponse(status=404)

        for book in books:
            book.delete()

        return HttpResponse(status=204)



@method_decorator(my_decorator, name='dispatch')  # 所有请求方式的小写方法装饰my_decorator装饰器
class RegisterView(View):
    """类视图:处理注册"""

    # 这里的函数名称需与请求方式的小写一致,方便View中dispatch函数指派对应的请求方式
    def get(self, request):
        """处理GET请求,返回注册页面"""
        print("get方法")
        return render(request, 'register.html')

    def post(self, request):
        """处理POST请求,实现注册逻辑"""
        print("post方法")
        return HttpResponse('这是实现注册逻辑')



# 增
class CreateView(View):
    def get(self, request):
        # 在BookInfo创建一条记录: 方式一
        book = BookInfo()
        book.btitle = "西游记"
        book.bpub_date = date(2018, 10, 17)
        book.save()

        # 在BookInfo创建一条记录: 方式二(推荐)
        book = BookInfo.objects.create(
            btitle="西厢记",
            bpub_date=date(2018, 10, 17),
            bread=10
        )

        return HttpResponse("ok")

# 查
class SelectView(View):
    def get(self, request):
    # def post(self, request):

        blist = BookInfo.objects.all()  # objects 类似flask中query, all()表示全部数据

        blist = BookInfo.objects.all().order_by("bread")  # 升序
        blist = BookInfo.objects.all().order_by("-id")  # 降序

        # print(BookInfo.objects.get(pk=100).btitle)  # get() 过滤单一结果(除此之外都会抛异常,没查到也抛出异常),  pk是primarykey的简写

        blist = BookInfo.objects.filter(id__exact=1)  # id_exact=1表示id=1(也可简写为id=1)的书

        blist = BookInfo.objects.filter(btitle__contains="传")  # 查询书名中包含"传"的书

        blist = BookInfo.objects.filter(btitle__endswith="部")  # 查询书名以"部"结尾的书

        blist = BookInfo.objects.filter(btitle__isnull=False)  # 判断书名不为空的书

        blist = BookInfo.objects.filter(id__in=[1, 3, 5])  # 查询id=1/3/5的书

        blist = BookInfo.objects.filter(pk__gt=3)  # pk大于3的书   gt大于  lt小于  gte大于等于  lte小于等于

        blist = BookInfo.objects.exclude(id=3)  # 查询除id=3之外的所有书(即id不等于3)

        blist = BookInfo.objects.filter(bpub_date__year=1980)  # 查询日期中年份=1980的书

        blist = BookInfo.objects.filter(bpub_date__gt=date(1990, 1, 1))  # 查询1980年1月1日之后的书

        blist = BookInfo.objects.filter(bread__gte=F("bcomment") * 2)  # 阅读量大于等于2倍评论量

        blist = BookInfo.objects.filter(Q(pk__lt=3) & Q(bread__gt=20))  # 与 使用Q对象,多个过滤条件时使用,Q之间使用与或非& | ~  连接
        blist = BookInfo.objects.filter(pk__lt=3, bread__gt=20)  # 如果只有与条件,则可这样简写

        blist = BookInfo.objects.filter(Q(pk__lt=3) | Q(bread__gt=20))  # 或

        blist = BookInfo.objects.filter(~Q(pk__lt=3))  # 非

        read_sum = BookInfo.objects.aggregate(Sum("bread"))  # 返回值{"bread__sum": 136} 查询图书的总阅读量(值相加)
        print(read_sum)

        restricted_read_count = BookInfo.objects.filter(bread__gt=20).count()
        print(restricted_read_count)

        return render(request, 'book.html', {"blist": blist})

# 查
class HeroView(View):
    def get(self,request):
        # 查询id为1的图书中的所有人物
        hlist = HeroInfo.objects.filter(hbook_id__lt=4).all()
        # print(type(hlist), hlist)
        # """
        # < class 'django.db.models.query.QuerySet'> < QuerySet[< HeroInfo: 郭靖 >, < HeroInfo: 黄蓉 >, < HeroInfo: 黄药师 >, < HeroInfo: 欧阳锋 >, < HeroInfo: 梅超风 >, < HeroInfo: 乔峰 >, < HeroInfo: 段誉 >, < HeroInfo: 虚竹 >, < HeroInfo: 王语嫣 >, < HeroInfo: 令狐冲 >, < HeroInfo: 任盈盈 >, < HeroInfo: 岳不群 >, < HeroInfo: 东方不败 >] >
        # """
        # for item in hlist:
        #     # <class 'users_1.models.HeroInfo'> 郭靖
        #     print(type(item),item)

        book = BookInfo.objects.get(pk=2)
        print(book, type(book))  # 天龙八部 <class 'users_1.models.BookInfo'>
        hlist = book.book_heros.all()
        print(hlist)  # <QuerySet [<HeroInfo: 乔峰>, <HeroInfo: 段誉>, <HeroInfo: 虚竹>, <HeroInfo: 王语嫣>]>
        bk = BookInfo.objects.filter(btitle="天龙八部").first().book_heros  # 注: QuerySet和
        print("bk-----", bk, type(bk))  # bk----- users_1.HeroInfo.None <class 'django.db.models.fields.related_descriptors.create_reverse_many_to_one_manager.<locals>.RelatedManager'> 注意: 这里有值!!!
        if bk:
            print("bk有值")
        print("bk-----",bk.all())

        tl = BookInfo.objects.filter(btitle="天龙八部").first().book_heros.all()
        print("tl-----", tl)

        return HttpResponse("OK")

# 改
class EditView(View):
    def post(self,request):
        # 修改
        # 方案一:  修改一个对象
        book = BookInfo.objects.get(pk=1)
        book.btitle = "斗破苍穹1111"
        book.save()
        # 方案二:  修改多个对象
        result = BookInfo.objects.filter(pk=1).update(btitle="斗罗大陆")  # result取得修改的行数
        print("result ", result)

        book = BookInfo.objects.create(
            btitle = "择天记",
            bpub_date = date(2014, 10, 18),
            bread = 1000,
            bcomment = 2000
        )
        hero = HeroInfo.objects.create(
            hname = "陈长生",
            hgender = 1,
            hbook = book
        )

        return HttpResponse("OK")

# 删
class DeleteView(View):
    def post(self,request):
        # 删除
        # 方案一:  删除一个对象
        # book = BookInfo.objects.get(pk=1)
        # book.delete()
        # 方案二:  删除多个对象
        result = BookInfo.objects.filter(pk=5).delete()   # result取得删除影响行数

        for item in BookInfo.objects.filter(btitle="天龙八部").first().book_heros.all():
            if item.hname == "乔峰":
                print(item, type(item))
                item.delete()

        return HttpResponse("OK")

"""
get()/aggregate() 方法会立即到mysql中查询,并将结果返回

all()/filter()/exclude()/order_by() 这些方法返回的结果类型为QuerySet,只进行查询语句的拼接,并不真的到mysql中查询,知道使用数据时,才会到mysql中查询,如与if/for等语句一起使用时(即在真正使用数据时)才真正查询,表现出惰性执行和缓存(在第一次使用查询处结果后进行缓存,以后再次查询时直接取缓存,但是这里也许会出现数据修改了而缓存还是修改前的,那么此时只需不要赋值就行),减少与mysql的交互次数(类似flask中的lazy="dynamic")
"""




# ------------------------------函数视图------------------------------

def index(request):
    """
    index视图
    :param request: 包含了请求信息的请求对象
    :return: 响应对象,要返回的字符串数据可以放在响应对象中
    """
    """
    构造响应对象:
    HttpResponse(content=响应体, content_type=响应体数据类型, status=状态码)
    通过HttpResponse对象属性来设置响应体/状态码. content:返回的内容   status_code:返回的HTTP响应状态码
    response["haha"] = "gaga"   自定义响应头haha,值为gaga
    """
    return HttpResponse("Hello World~~~")


def say(request):
    url = reverse('users_1:news_index')  # 返回/users_1/index/
    """
    在视图函数中需要使用url时,如指向一个地址使用return HttpResponseRedirect("index"),此时可以采用反向解析reverse()
    reverse('users_1:news_index')  这里users表示users子应用路由的命名空间名,news_index表示users子应用路由中命名为news_index的路由(视图函数)
    """
    print(url)
    return HttpResponse("adgfdgfj")
    # return HttpResponseRedirect(reverse('users_1:news_index'))  #重定向
    # return redirect(reverse('users_1:news_index'))  #重定向, 这里可以把reverse('users_1:news_index')换成绝对路径


# 这里在urls的正则表达式中若未定义参数,按顺序传递;若定义了参数,则按参数名传递
def weather(request, city, year):
    print("city: ", city)
    print("year: ", year)
    return HttpResponse('OK')


# 获取请求路径中的查询字符串参数/users_1/qs/?a=1&b=2&a=3
def qs_params(request):
    a = request.GET.get('a')
    b = request.GET.get('b')
    alist = request.GET.getlist('a')
    print(a)  # 3
    print(b)  # 2
    print(alist)  # ['1', '3']
    return HttpResponse("OK")


def demo_view(request):
    response = HttpResponse('ok')
    # 设置cookie
    response.set_cookie("cookie_name", "cookie_value", max_age=None)  # 临时cookie,关闭浏览器即删除,单位秒

    # 设置session,若value是整数则在无活动value秒后过期,若value为0则在关闭浏览器时过期,若value为None则为默认有效期---两周
    # 设置过期时间在setting.py中设置SESSION_COOKIE_AGE(全局默认值)
    # 在session键值保存到redis中时会被加密
    request.session["key_session"] = "value_session"
    return response


def demo_view_out(request):
    # 读取cookie
    cookie1 = request.COOKIES.get("cookie_name")
    print(cookie1)

    # 删除指定键值
    del request.session["cookie_name"]

    # 清除所有session,在存储中删除值部分,键还存在
    # request.session.clear()

    # 清除session数据.在存储中删除session整条数据
    request.session.flush()

    # 获取session中键的值
    print(request.session.get('key_session', "111"))
    return HttpResponse("OK")


# -------------------------模板渲染示例-----------------------------
# 整体模板语法与flask类似
def template_simple(request):
    # 获取模板
    template = loader.get_template('template_simple.html')
    context = {
        "city": "beijing"
    }
    # 渲染模板
    return HttpResponse(template.render(context))

# # 简写
# def template_simple(request):
#     context = {
#         "city": "beijing"
#     }
#     return render(request, 'template_simple.html',context)
